package ua.yandex.collections;

import java.util.Arrays;

public class MyArrayList implements MyList {
    public static final int DEFAULT_CAPACITY = 10;
    public static final int FOLDING_FACTOR = 4;

    private int size;
    private Object[] holder;

    public MyArrayList(int capacity) {
        holder = new Object[capacity];
    }

    public MyArrayList() {
        this(DEFAULT_CAPACITY);
    }

    @Override
    public void add(Object e) {
        ensureCapacity(size + 1);
        holder[size++] = e;
    }

    @Override
    public void add(int index, Object e) {
        checkIndexBounds(index);
        ensureCapacity(size + 1);
        System.arraycopy(holder, index, holder, index + 1, size - index + 1);
        holder[index] = e;
        size++;
    }

    @Override
    public void addAll(Object[] c) {
        int addingCollectionLength = c.length;
        ensureCapacity(size + addingCollectionLength);
        System.arraycopy(c, 0, holder, size, addingCollectionLength);
        size += addingCollectionLength;
    }

    @Override
    public void addAll(int index, Object[] c) {
        checkIndexBounds(index);
        int addingCollectionLength = c.length;
        ensureCapacity(size + addingCollectionLength);
        System.arraycopy(holder, index,
                holder, index + 1, addingCollectionLength);
        System.arraycopy(c, 0, holder, index, addingCollectionLength);
        size += addingCollectionLength;
    }

    @Override
    public Object get(int index) {
        checkIndexBounds(index);
        return holder[index];
    }

    @Override
    public Object remove(int index) {
        checkIndexBounds(index);
        Object e = holder[index];

        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(holder, index + 1, holder, index, numMoved);
        }

        holder[--size] = null;
        foldHolder();

        return e;
    }

    private void foldHolder() {
        if (holder.length / Math.max(size, 1) < FOLDING_FACTOR) {
            return;
        }

        int decreasedLength = holder.length / 2;
        holder = Arrays.copyOfRange(holder, 0, decreasedLength);
    }

    @Override
    public void set(int index, Object e) {
        checkIndexBounds(index);
        holder[index] = e;
    }

    private void checkIndexBounds(int index) {
        if (index >= size || index < 0) {
            String template = "Non-existent index <%d> on list of size %d";
            String message = String.format(template, index, size);
            throw new IndexOutOfBoundsException(message);
        }
    }

    @Override
    public int indexOf(Object o) {
        for (int i = 0; i < size; i++) {
            if (o.equals(holder[i])) {
                return i;
            }
        }

        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        holder = new Object[]{};
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Object[] toArray() {
        return Arrays.copyOfRange(holder, 0, size);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        String separator = "";

        sb.append('[');
        for (int i = 0; i < size; i++) {
            sb.append(separator)
                    .append(holder[i]);
            separator = ",";
        }
        sb.append(']');

        return sb.toString();
    }

    public void ensureCapacity(int addingIndex) {
        if (holder.length > addingIndex) {
            return;
        }

        int increasedLength = Math.max(holder.length, 1) * 2;
        increasedLength = Math.max(increasedLength, addingIndex);
        Object[] increasedHolder = new Object[increasedLength];
        System.arraycopy(holder, 0, increasedHolder, 0, size);
        holder = increasedHolder;
    }

}
